﻿Public Class Snake
    Dim workSpace As Bitmap
    Dim picture As Graphics
    Dim player As SnakeBody
    Dim food As Food
    '加载窗口时的方法
    Private Sub Snake_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        '初始化暂停标签位置
        pauseLabel.Location = New Point((Constants.width - pauseLabel.Width) / 2, (Constants.height - pauseLabel.Height) / 2)
        '初始化蛇
        player = New SnakeBody()
        player.setDirection(4) '方向默认为右
        player.setVelocity(Constants.speed) '从“常量池”中获取速度
        '初始化食物
        food = New Food()
        updateFood()
        '设定时间间隔
        Timer1.Interval = 1000 / player.getVelocity
        '初始化窗口
        Me.ClientSize = New Size(Constants.width, Constants.height)
        PictureBox1.Size = New Size(Constants.width, Constants.height)
        '初始化背景
        setBgimg()
        showSnake()
        '计时器启动
        Timer1.Start()
    End Sub
    Private Sub setBgimg()
        '画出栅格（背景）
        workSpace = New Bitmap(Constants.width, Constants.height)
        picture = Graphics.FromImage(workSpace)
        '横线
        For i = 1 To Int(Constants.height / Constants.latticeSize) - 1
            Dim y As Int32 = Constants.latticeSize * i
            picture.DrawLine(New Pen(Color.FromArgb(95, 95, 95)), 0, y, Constants.width, y)
        Next i
        '竖线
        For i = 1 To Int(Constants.width / Constants.latticeSize) - 1
            Dim x As Int32 = Constants.latticeSize * i
            picture.DrawLine(New Pen(Color.FromArgb(95, 95, 95)), x, 0, x, Constants.height)
        Next i
        PictureBox1.Image = workSpace
    End Sub
    '显示蛇
    Private Sub showSnake()
        '图片导入
        Dim headImg As Bitmap = My.Resources.head
        Dim bodyImg As Bitmap = My.Resources.body
        Dim foodImg As Bitmap = My.Resources.food
        '位置变量
        Dim posx As Int32
        Dim posy As Int32
        '从头结点开始
        Dim p As SnakeNode = player.head
        posx = p.getx * latticeSize
        posy = p.gety * latticeSize
        picture.DrawImage(headImg, posx, posy, Constants.latticeSize, Constants.latticeSize)
        p = p.nextNode
        While (Not (p Is player.head))
            posx = p.getx * latticeSize
            posy = p.gety * latticeSize
            picture.DrawImage(bodyImg, posx, posy, Constants.latticeSize, Constants.latticeSize)
            p = p.nextNode
        End While
        '显示食物
        posx = food.getx() * latticeSize
        posy = food.gety() * latticeSize
        picture.DrawImage(foodImg, posx, posy, Constants.latticeSize, Constants.latticeSize)
    End Sub
    Private Sub snakeMove(direction As Int32)
        'p指向尾结点
        Dim p As SnakeNode
        p = player.head.preNode
        While (p IsNot player.head)
            p.setx(p.preNode.getx())
            p.sety(p.preNode.gety())
            p = p.preNode
        End While
        '退出循环时，p表示头结点，此时移动头结点
        '考虑到边界判定运算式比较长，因此定义临时变量，增强可读性
        Dim newP As Int32
        Select Case direction
            '上
            Case 1
                newP = p.gety() - 1
                If (newP < 0) Then
                    newP = newP + (Constants.height / Constants.latticeSize)
                End If
                p.sety(newP)
            '左
            Case 2
                newP = p.getx() - 1
                If (newP < 0) Then
                    newP = newP + (Constants.width / Constants.latticeSize)
                End If
                p.setx(newP)
            '下
            Case 3
                newP = p.gety() + 1
                If (newP >= Constants.height / Constants.latticeSize) Then
                    newP = newP - (Constants.height / Constants.latticeSize)
                End If
                p.sety(newP)
            '右
            Case 4
                newP = p.getx() + 1
                If (newP >= Constants.width / Constants.latticeSize) Then
                    newP = newP - (Constants.width / Constants.latticeSize)
                End If
                p.setx(newP)
        End Select
        '移动后，判断是否进食
        eatFood()
        '死亡判定
        p = player.head.nextNode
        While (p IsNot player.head)
            If (player.head.getx() = p.getx() And player.head.gety() = p.gety()) Then
                Timer1.Enabled = False
                Dim sss As String = "游戏结束！" + vbCrLf _
                + "得分：" + Str(player.getScore()) + vbCrLf _
                + "长度：" + Str(player.getLength())
                Dim c As Int32 = MsgBox(sss, 0, "")
                If (c = 1) Then
                    Me.Close()
                End If
            End If
            p = p.nextNode
        End While
    End Sub
    '计时器触发事件
    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        snakeMove(player.getDirection())
        setBgimg()
        showSnake()
    End Sub
    '按下按键时触发事件
    Private Sub Snake_KeyDown(sender As Object, e As KeyEventArgs) Handles MyBase.KeyDown
        '首先将keycode转换为代表方向的参数
        Dim arg As Int32
        Select Case e.KeyCode
            Case 38 'Up Arrow
                arg = 1
            Case 37 'Left Arrow
                arg = 2
            Case 40 'Down Arrow
                arg = 3
            Case 39 'Right Arrow
                arg = 4
        End Select
        '根据当前方向，确定是否改变移动方向
        If ((player.getDirection() = 1 Or player.getDirection() = 3) And (arg = 2 Or arg = 4)) Then
            player.setDirection(arg)
        ElseIf ((player.getDirection() = 2 Or player.getDirection() = 4) And (arg = 1 Or arg = 3)) Then
            player.setDirection(arg)
        Else
            Return
        End If
        '如果在暂停状态，立刻继续
        If (pauseLabel.Visible = True) Then
            pauseLabel.Visible = False
        End If
        '按下方向键时立刻移动，并不给计时器留下连续移动两格的机会
        snakeMove(player.getDirection())
        Timer1.Enabled = False
        Timer1.Enabled = True
        setBgimg()
        showSnake()
    End Sub
    '暂停/继续游戏
    Private Sub PictureBox1_Click(sender As Object, e As EventArgs) Handles PictureBox1.Click
        Timer1.Enabled = Not Timer1.Enabled
        pauseLabel.Visible = Not pauseLabel.Visible
    End Sub
    '更新食物位置
    Private Sub updateFood()
        Dim rand As New Random()
        Dim newx As Int32 = rand.Next(Constants.width / Constants.latticeSize)
        Dim newy As Int32 = rand.Next(Constants.height / Constants.latticeSize)
        '如果位置不合理，则重新生成，直到合理为止
        'While (isOverlap(newx, newy))
        'newx = rand.Next(Constants.width / Constants.latticeSize)
        'newy = rand.Next(Constants.height / Constants.latticeSize)
        'End While
        '更新位置
        food.setPos(newx, newy)
    End Sub
    '判断是否进食并更新长度
    Private Sub eatFood()
        If (player.head.getx() = food.getx() And player.head.gety() = food.gety()) Then
            Dim x As Int32
            Dim y As Int32
            x = player.head.preNode.getx()
            y = player.head.preNode.gety()
            '尾插法，增长蛇身
            Dim p As New SnakeNode(x, y)
            player.head.preNode.nextNode = p
            p.nextNode = player.head
            p.preNode = player.head.preNode
            player.head.preNode = p
            '进食后，更新食物位置
            updateFood()
            '更新分数
            player.addScore(10, 3)
            '更新长度
            player.addLength()
        End If
    End Sub
    '判断食物生成是否与玩家重叠
    Private Function isOverlap(x As Int32, y As Int32) As Boolean
        Dim p As SnakeNode = player.head
        While (p.nextNode IsNot player.head)
            If ((p.getx() = x) Or (p.gety() = y)) Then
                Return True
            End If
            p = p.nextNode
        End While
        Return False
    End Function
End Class
